package org.egrit;

import java.awt.Point;
import java.awt.Rectangle;

/**
 * Represents the bounds of a rectangular grid region. 
 * 
 * @author michael
 */
public class RectGridBounds implements IGridBounds {

    /*
     * We delegate to an instance of either the nested 
     * DelegateRectGridBounds class or EmptyGridBounds.
     */
    private final IGridBounds delegate;
    

    /*
     * Creates a new bounds instance.
     */
    public RectGridBounds(int minGridX, int maxGridX, int minGridY, int maxGridY) {
        Rectangle rect = createOuterBounds(minGridX, maxGridX, minGridY, maxGridY);
        if (rect.isEmpty()) {
            delegate = new EmptyGridBounds();
        } else {
            delegate = new DelegateRectGridBounds(rect);
        }
    }

    @Override
    public boolean isEmpty() {
        return delegate.isEmpty();
    }

    @Override
    public int getWidth() {
        return delegate.getWidth();
    }

    @Override
    public int getHeight() {
        return delegate.getHeight();
    }

    @Override
    public int getArea() {
        return delegate.getArea();
    }

    @Override
    public IGridIterator getIterator() {
        return delegate.getIterator();
    }

    @Override
    public Point getMinCellPos() {
        return delegate.getMinCellPos();
    }

    @Override
    public Point getMaxCellPos() {
        return delegate.getMaxCellPos();
    }

    @Override
    public int getMinGridX() {
        return delegate.getMinGridX();
    }

    @Override
    public int getMaxGridX() {
        return delegate.getMaxGridX();
    }

    @Override
    public int getMinGridY() {
        return delegate.getMinGridY();
    }

    @Override
    public int getMaxGridY() {
        return delegate.getMaxGridY();
    }

    @Override
    public Rectangle getOuterRect() {
        return delegate.getOuterRect();
    }

    @Override
    public boolean contains(IGridBounds other) {
        return delegate.contains(other);
    }

    @Override
    public boolean containsCell(Point gridPos) {
        return delegate.containsCell(gridPos);
    }

    @Override
    public boolean containsCell(int gridX, int gridY) {
        return delegate.containsCell(gridX, gridY);
    }

    @Override
    public IGridBounds intersection(IGridBounds other) {
        return delegate.intersection(other);
    }

    @Override
    public IGridBounds intersection(Rectangle rect) {
        return delegate.intersection(rect);
    }

    @Override
    public boolean intersects(IGridBounds other) {
        return delegate.intersects(other);
    }

    @Override
    public int hashCode() {
        return delegate.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return delegate.equals(obj);
    }

    @Override
    public String toString() {
        return delegate.toString();
    }

    private Rectangle createOuterBounds(int minGridX, int maxGridX, int minGridY, int maxGridY) {
        int widthCells = maxGridX - minGridX + 1;
        int heightCells = maxGridY - minGridY + 1;

        if (widthCells < 1 || heightCells < 1) {
            return new Rectangle();
        } else {
            return new Rectangle(minGridX, minGridY, widthCells, heightCells);
        }
    }


    /**
     * Delegate class representing non-empty bounds.
     */
    private static class DelegateRectGridBounds implements IGridBounds {
        private final Rectangle outerBounds;
        private final Point maxCellPos;

        private DelegateRectGridBounds(Rectangle rect) {
            if (rect == null || rect.isEmpty()) {
                // just in case
                throw new IllegalArgumentException(
                        "Delegate bounds cannot be initialized from a null or empty rectangle");
            }
            
            this.outerBounds = rect;
            this.maxCellPos = new Point(
                    rect.x + rect.width - 1,
                    rect.y + rect.height - 1);
        }

        @Override
        public int getWidth() {
            return outerBounds.width;
        }

        @Override
        public int getHeight() {
            return outerBounds.height;
        }

        @Override
        public int getArea() {
            return outerBounds.width * outerBounds.height;
        }

        @Override
        public int getMinGridX() {
            return outerBounds.x;
        }

        @Override
        public int getMinGridY() {
            return outerBounds.y;
        }

        @Override
        public Point getMinCellPos() {
            return new Point(getMinGridX(), getMinGridY());
        }

        @Override
        public int getMaxGridX() {
            return maxCellPos.x;
        }

        @Override
        public int getMaxGridY() {
            return maxCellPos.y;
        }

        @Override
        public Point getMaxCellPos() {
            return new Point(maxCellPos);
        }

        @Override
        public Rectangle getOuterRect() {
            return new Rectangle(outerBounds);
        }

        @Override
        public boolean containsCell(Point gridPos) {
            if (gridPos == null) {
                throw new IllegalArgumentException("argument must not be null");
            }

            return containsCell(gridPos.x, gridPos.y);
        }

        @Override
        public boolean containsCell(int gridX, int gridY) {
            return outerBounds.contains(gridX, gridY);
        }

        @Override
        public IGridBounds intersection(IGridBounds other) {
            if (other == null || other.isEmpty()) {
                return GridBoundsFactory.create();
            }

            return GridBoundsFactory.create(outerBounds.intersection(other.getOuterRect()));
        }

        @Override
        public IGridBounds intersection(Rectangle rect) {
            if (rect == null || rect.isEmpty()) {
                return GridBoundsFactory.create();
            }

            return GridBoundsFactory.create(outerBounds.intersection(rect));
        }

        @Override
        public boolean intersects(IGridBounds other) {
            if (other == null || other.isEmpty()) {
                return false;
            }

            return outerBounds.intersects(other.getOuterRect());
        }

        @Override
        public boolean contains(IGridBounds other) {
            if (other == null || other.isEmpty()) {
                return false;
            }

            return outerBounds.contains(other.getOuterRect());
        }

        @Override
        public boolean isEmpty() {
            return false;
        }

        @Override
        public GridBoundsIterator getIterator() {
            return new GridBoundsIterator(this);
        }

        /**
         * Tests equality this instance and another object. <p> Note that an
         * {@linkplain IGridBounds} object can be equal to this instance even if it is of a
         * different class since equality is assessed on the basis of bounds location and size.
         *
         * @param obj the other object
         *
         * @return true if the other object represents the same bounds as this instance
         */
        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }

            if (!(obj instanceof IGridBounds)) {
                return false;
            }

            IGridBounds other = (IGridBounds) obj;

            if (other.isEmpty()) {
                return false;
            }

            return outerBounds.equals(other.getOuterRect());
        }

        @Override
        public int hashCode() {
            return 97 + outerBounds.hashCode();
        }

        @Override
        public String toString() {
            return String.format("GridBounds[(%d,%d) to (%d,%d)]",
                    getMinGridX(),
                    getMinGridY(),
                    getMaxGridX(),
                    getMaxGridY());
        }
    }
}
